"""
Can analysis the f1_score and the accuracy
                    Ground Truth Positive                 Ground Truth Negative
Predict Positive         TP                                  FP
Predict Negative         FN                                  TN

precision = TP / (TP + FP)
recall = TP / (TP + FN)
accuracy = (TP + TN) / ALL
F_1 = 2 * precision * recall / (precision + recall)
"""
class Evaluate(object):
    def __init__(self, ground_truth_label):
        self.ground_truth_label = ground_truth_label
        self.max_accuracy = 0
        self.max_F1 = 0
        self.max_f1_epoch = 0
        self.max_acc_epoch = 0
        self.num0 = 0
        self.num1 = 0
        self.num2 = 0
        self.get_ground_truth_num()

    def get_ground_truth_num(self):
        """
        :return: the result of TP + FN
        """
        temp = [0] * 3
        for label in self.ground_truth_label:
            temp[label] += 1
        self.num0 = temp[0]
        self.num1 = temp[1]
        self.num2 = temp[2]

    def get_f1_score(self, predict_label, epoch):
        predict_label = self.get_predict_label(predict_label)
        predict_num = [0] * 3 # count the number of TP + FP
        predict_true_num = [0] * 3 # count the number of TP
        for g,p in zip(self.ground_truth_label, predict_label):
            predict_num[p] += 1
            if (g == p):
                predict_true_num[g] += 1
        # calculate the f1 score and accuracy
        p_0, r_0, f1_0 = self.calculate_f_score(predict_true_num[0], predict_num[0], self.num0)
        p_1, r_1, f1_1 = self.calculate_f_score(predict_true_num[1], predict_num[1], self.num1)
        p_2, r_2, f1_2 = self.calculate_f_score(predict_true_num[2], predict_num[2], self.num2)
        accuracy = self.calculate_accuracy(predict_true_num, predict_num)
        if self.max_accuracy < accuracy:
            self.max_accuracy = accuracy
            self.max_acc_epoch = epoch
        temp_F = (f1_0 + f1_1 + f1_2) / 3
        if self.max_F1 < temp_F:
            self.max_F1 = temp_F
            self.max_f1_epoch = epoch
            self.save_result(predict_label, 'epoch_' + str(epoch) + '_predict_label.txt')
        if epoch % 5 == 0:
            print('current class 0 evaluate result:')
            print('f1: %6.3f,  precision: %6.3f,  recall: %6.3f' % (f1_0, p_0, r_0))
            print('current class 1 evaluate result:')
            print('f1: %6.3f,  precision: %6.3f,  recall: %6.3f' % (f1_1, p_1, r_1))
            print('current class 2 evaluate result:')
            print('f1: %6.3f,  precision: %6.3f,  recall: %6.3f' % (f1_2, p_2, r_2))
            print('current accuracy: %6.3f' % accuracy)
            print('current max accuracy: %6.3f' % self.max_accuracy)
            print('max f1 score %6.3f gained at %d epoch' % (self.max_F1, self.max_f1_epoch))
            print('max accuracy %6.3f gained at %d epoch' % (self.max_accuracy, self.max_acc_epoch))
        return self.max_F1, self.max_accuracy
    def calculate_f_score(self, TP, TPFP, TPFN):
        if TP == 0:
            return 0, 0, 0
        precision = float(TP) / TPFP
        recall = float(TP) / TPFN
        if precision == 0 or recall == 0:
            return 0, 0, 0
        f1 = 2 * precision * recall / (precision + recall)
        return  precision, recall, f1

    def calculate_accuracy(self, predicts_true, predicts):
        temp1 = temp2 = 0
        for i in predicts: # TP + FN
            temp1 += i
        for i in predicts_true: # TP + FP + FN + TN
            temp2 += i
        if temp2 == 0 or temp1 == 0:
            return 0
        return float(temp2) / temp1

    def save_result(self,labels, filename=''):
        with open(filename, 'w') as file:
            for label in labels:
                file.write(str(label) + '\n')

    def get_predict_label(self, predicts):
        labels = []
        for predict in predicts:
            index = 0
            max = -100
            for i, score in enumerate(predict):
                if (score > max):
                    max = score
                    index = i
            labels.append(index)
        return labels
